/**
 * 
 */
package javapop.framework.generic;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javapop.framework.DefaultParseContext;
import javapop.framework.Grammar;
import javapop.framework.ParseContext;
import javapop.framework.ParseInput;
import javapop.framework.ParseResult;
import javapop.framework.Parser;
import javapop.framework.generic.tool.GrammarGenerator;
import javapop.framework.generic.tool.GrammarVisitorException;
import javapop.framework.input.StringParseInput;
import javapop.framework.parser.Choice;
import javapop.framework.parser.Forget;
import javapop.framework.parser.ForgetAll;
import javapop.framework.parser.LineComment;
import javapop.framework.parser.ListOf;
import javapop.framework.parser.Literal;
import javapop.framework.parser.Many;
import javapop.framework.parser.Maybe;
import javapop.framework.parser.MaybeParse;
import javapop.framework.parser.RegexpString;
import javapop.framework.parser.Reserved;
import javapop.framework.parser.TupleParser;
import javapop.framework.parser.WhiteSpace;
import javapop.framework.parser.XForm;
import javapop.framework.parser.tuple.Five;
import javapop.framework.parser.tuple.One;
import javapop.framework.parser.tuple.Three;
import javapop.framework.parser.tuple.Two;
import javapop.utils.Pair;
import javapop.utils.Quintuple;
import javapop.utils.Triple;

/**
 * @author hakim
 * 
 */
public class GenericGrammar extends Grammar {

	/**
	 * 
	 */
	public GenericGrammar() {
		creatGrammar();

		checkReferences();
	}

	private void creatGrammar() {
		Parser<Object> spaces = new Forget(new Many<Character>(
				WhiteSpace.getInstance()));
		register("spaces", spaces);

		Parser<Object> lineComment = new Forget(new LineComment(new Literal(
				"//")));
		register("lineComment", lineComment);

		Parser<Object> skipPart = new ForgetAll(ref("spaces"), new Maybe<Object>(
				ref("lineComment")));
		Parser<List<Object>> skip = new Many<Object>((Parser<Object>) skipPart);
		register("skip", skip);
		skip(ref("skip"));

		Set<String> reservedKeywords = new HashSet<String>();
		reservedKeywords.add("@java");
		reservedKeywords.add("@keywords");
		reservedKeywords.add("@whitespace");
		reservedKeywords.add("@linecomment");
		reservedKeywords.add("@skip");
		reservedKeywords.add("@regexpwithoutkeywords");
		reservedKeywords.add("@regexp");
		reservedKeywords.add("@transform");
		reservedKeywords.add("@ignore");
		reservedKeywords.add("@literal");
		reservedKeywords.add("@character");
		reservedKeywords.add("@whitespace");

		
		
		Parser<String> string = new XForm<String, String>(new RegexpString(
				"\"[^\"]*\""
				//".*"
				)) {
			@Override
			public String transform(ParseContext<?> arg0, String arg) {
				if (arg.length() == 2)
					return "";
				else
					return arg.substring(1, arg.length() - 1);
			}
		};
		register("string", string);
		
		Parser<String> directstring = new XForm<String, String>(new RegexpString(
				"\".*\""
				//".*"
				)) {
			@Override
			public String transform(ParseContext<?> arg0, String arg) {
				if (arg.length() == 2)
					return "";
				else
					return arg.substring(1, arg.length() - 1);
			}
		};
		register("directstring", directstring);
		

		Parser<String> cstring = new XForm<String, String>(new RegexpString(
				"'.'")) {
			@Override
			public String transform(ParseContext<?> arg0, String arg) {
				if (arg.length() == 2)
					throw new RuntimeException("No character found!!!");
				else
					return arg.substring(1, arg.length() - 1);
			}
		};
		register("cstring", cstring);

		Parser<String> code = new XForm<String, String>(new RegexpString(
				"\\{[^\\{]*\\}")) {
			@Override
			public String transform(ParseContext<?> arg0, String arg) {
				if (arg.length() == 2)
					return "";
				else
					return arg.substring(1, arg.length() - 1);
			}
		};
		register("code", code);

		// --- les types
		Parser<String> type = new Reserved<String>(new RegexpString(
				//"[a-zA-Z_]\\w*([.][a-zA-Z_]\\w*)*(\\[\\])?"), reservedKeywords);
				"[a-zA-Z_]\\w*([.][a-zA-Z_]\\w*)*((\\[\\])|(<\\s*[a-zA-Z_]\\w*([.][a-zA-Z_]\\w*)*(\\s*,\\s*[a-zA-Z_]\\w*([.][a-zA-Z_]\\w*)*)*\\s*>))?"), reservedKeywords);
		
		register("type", type);

		// --- les mots cles
		Parser<String> keyword = new Reserved<String>(new RegexpString(
				"[a-zA-Z_@#~][a-zA-Z0-9_@#~]*"), reservedKeywords);
		register("keyword", keyword);

		// --- les mots cles
		Parser<String> ident = new Reserved<String>(new RegexpString(
				"[a-zA-Z][a-zA-Z0-9]*"), reservedKeywords);
		register("ident", ident);

		Parser<GDefRegle> defregle = new XForm<Pair<String, String>, GDefRegle>(
				new Two<String, String>().then(character('<'))
						.first(this.<String> ref("ident")).then(character(':'))
						.second(this.<String> ref("type")).then(character('>'))) {

			@Override
			public GDefRegle transform(ParseContext<?> ctx,
					Pair<String, String> content) {
				return new GDefRegle(content.getFirst(), content.getSecond());
			}
		};
		register("defregle", defregle);

		
		// ensemble des parsers
		parserRef();
		parserXForm();
		parserForget();
		parserRegexp();
		parserRegexpString();
		parserLiteral();
		parserCharacter();
		parserMaybeOrDefault();
		parserListOf();
		parserChoice();
		parserTuple();
		parserMeta();
		parserKeywords();
		
		// regles
		regle();
		regles();
		
		
		// expressions
		parserDefExprParser();
		parserExprOperand();
		parserExprOperator();
		parserExprBracket();
		
		parserGrammar();
		
		gparser();
	}
	
	private void parserKeywords() {
		Parser<GKeyword> trans = new XForm<String,GKeyword>(this.<String>ref("keyword"))
		{
			public GKeyword transform(javapop.framework.ParseContext<?> ctx, String content) {
				return new GKeyword(content);
			};
		};
		
		ListOf<GKeyword> listkeyword = new ListOf<GKeyword>(trans);
		listkeyword.sep(character(','));
		
		Parser<GKeywords> keywords = new XForm<List<GKeyword>,GKeywords>( new One<List<GKeyword>>()
			.then(literal("@keywords"))
			.then(character('('))
			.parser(listkeyword)
			.then(character(')'))
			.then(character(';'))) {
			@Override
			public GKeywords transform(ParseContext<?> ctx,
					List<GKeyword> content) {
				return new GKeywords(content);
			}
		};
		register("keywords",keywords);
	}

	private void parserRef() {
		// ---
		Parser<GRefParser> refregle = new XForm<String, GRefParser>(
				new RegexpString("<[a-zA-Z_]\\w*>")) {
			@Override
			public GRefParser transform(ParseContext<?> arg0, String arg) {
				if (arg.length() == 2)
					throw new RuntimeException("No reference !");
				else
					return new GRefParser(arg.substring(1, arg.length() - 1));
			}
		};
		register("refregle", refregle);
	}

	private void parserXForm() {
		Parser<GTransform> transform = new XForm<Pair<String, GParser>, GTransform>(
				new Two<String, GParser>().then(literal("@transform"))
						.then(character('(')).first(this.<String> ref("ident"))
						.then(character(','))
						.second(this.<GParser> ref("gparser"))
						.then(character(')'))) {
			@Override
			public GTransform transform(ParseContext<?> ctx,
					Pair<String, GParser> p) {
				return new GTransform(p.getFirst(), p.getSecond());
			}
		};
		register("transform", transform);

	}

	private void parserForget() {
		Parser<GIgnore> ignore = new XForm<GParser, GIgnore>(new One<GParser>()
				.then(literal("@ignore")).then(character('('))
				.parser(this.<GParser> ref("gparser")).then(character(')'))) {
			@Override
			public GIgnore transform(ParseContext<?> ctx, GParser p) {
				return new GIgnore(p);
			}
		};
		register("ignore", ignore);
	}

	private void parserRegexp() {
		Parser<GRegexp> regexp = new XForm<String, GRegexp>(new One<String>()
				.then(literal("@regexp")).then(character('('))
				.parser(this.<String> ref("directstring")).then(character(')'))) {
			@Override
			public GRegexp transform(ParseContext<?> ctx, String content) {
				return new GRegexp(content);
			}
		};
		register("regexp", regexp);
	}

	private void parserRegexpString() {
		Parser<GRegexpWithoutKeywords> regexpwithoutkeywords = new XForm<String, GRegexpWithoutKeywords>(
				new One<String>().then(literal("@regexpwithoutkeywords"))
						.then(character('('))
						.parser(this.<String> ref("directstring"))
						.then(character(')'))) {
			@Override
			public GRegexpWithoutKeywords transform(ParseContext<?> ctx,
					String content) {
				return new GRegexpWithoutKeywords(content);
			}
		};
		register("regexpwithoutkeywords", regexpwithoutkeywords);
	}

	private void parserLiteral() {
		Parser<GLiteral> literal = new XForm<String, GLiteral>(
				new One<String>().then(literal("@literal"))
						.then(character('('))
						.parser(this.<String> ref("string"))
						.then(character(')'))) {
			public GLiteral transform(ParseContext<?> ctx, String content) {
				return new GLiteral(content);
			};
		};
		register("literal", literal);
	}

	private void parserCharacter() {
		Parser<GLiteral> character = new XForm<String, GLiteral>(
				new One<String>().then(literal("@character"))
						.then(character('('))
						.parser(this.<String> ref("cstring"))
						.then(character(')'))) {
			public GLiteral transform(ParseContext<?> ctx, String content) {
				return new GLiteral(content);
			};
		};
		register("character", character);
	}

	private void parserMaybeOrDefault() {
		Parser<GMaybe> regle = new XForm<Pair<GParser, MaybeParse<String>>, GMaybe>(
				new Two<GParser, MaybeParse<String>>().then(literal("@maybe"))
						.then(character('('))
						.first(this.<GParser> ref("gparser"))
						.second(new Maybe<String>(new One<String>().then(character(',')).parser(this.<String> ref("ident"))))
						.then(character(')'))) {
			@Override
			public GMaybe transform(ParseContext<?> ctx,
					Pair<GParser, MaybeParse<String>> content) {
				if(content.getSecond().hasResult()) {
					return new GMaybeOrDefault(content.getFirst(),
							content.getSecond().getResult().getResult());
				}
				else {
					return new GMaybe(content.getFirst());
				}
			}
		};
		register("maybe", regle);
	}


	private void parserListOf() {
		Parser<GListOf> listof = new XForm<Pair<GParser, String>, GListOf>(
				new Two<GParser, String>().then(literal("@list"))
						.then(character('('))
						.first(this.<GParser> ref("gparser"))
						.then(character(','))
						.second(this.<String> ref("string"))
						.then(character(')'))) {
			@Override
			public GListOf transform(ParseContext<?> ctx,
					Pair<GParser, String> content) {
				return new GListOf(content.getFirst(), content.getSecond());
			}
		};
		register("listof", listof);
		// loft.sep(new
		// Zero().then(ref("skip")).then(character(',')).then(ref("skip")));
	}
	
	private void parserTuple() {
		ListOf<GParser> listof = new ListOf<GParser>(this.<GParser>ref("gparser"));
		listof.sep(literal(","));
		
		Parser<GTuple> tuple = new XForm<List<GParser>, GTuple>(
				new One<List<GParser>>().
					then(literal("@seq"))
					.then(character('('))
					.parser(listof)
					.then(character(')'))
				
		) {
			@Override
			public GTuple transform(ParseContext<?> ctx, List<GParser> content) {
				return new GTuple(content);
			}
		};
		register("tuple",tuple);
	}

	private void parserChoice() {
		ListOf<GParser> listof = new ListOf<GParser>(
				this.<GParser> ref("gparser"),1,Integer.MAX_VALUE);
		listof.sep(character(','));
		
		Parser<GChoice> choice = new XForm<List<GParser>, GChoice>(
				new One<List<GParser>>().then(literal("@choice"))
						.then(character('(')).parser(listof)
						.then(character(')'))) {
			@Override
			public GChoice transform(ParseContext<?> ctx, List<GParser> content) {
				return new GChoice(content);
			}
		};

		register("choice", choice);
	}
	
	
	private void gparser() {
		Choice<GParser> gparser = new Choice<GParser>();
		gparser.either(this.<GParser>ref("tuple"));
		gparser.orElse(this.<GParser>ref("transform"));
		gparser.orElse(this.<GParser>ref("ignore"));
		gparser.orElse(this.<GParser>ref("regexp"));
		gparser.orElse(this.<GParser>ref("regexpwithoutkeywords"));
		gparser.orElse(this.<GParser>ref("literal"));
		gparser.orElse(this.<GParser>ref("character"));
		
		gparser.orElse(this.<GParser>ref("maybe"));
		gparser.orElse(this.<GParser>ref("listof"));
		gparser.orElse(this.<GParser>ref("choice"));
		gparser.orElse(this.<GParser>ref("refregle"));
		
		register("gparser",gparser);
	}
	
	private void regle() {
		// --- def regle complete
		Parser<GRegle> regle = new XForm<Pair<GDefRegle, GParser>, GRegle>(
				new Two<GDefRegle, GParser>()
						.first(this.<GDefRegle> ref("defregle"))
						.then(literal(":="))
						.second(this.<GParser> ref("gparser"))
						.then(literal(";"))) {
			@Override
			public GRegle transform(ParseContext<?> ctx,
					Pair<GDefRegle, GParser> content) {
				return new GRegle(content.getFirst(), content.getSecond());
			}
		};
		register("regle", regle);
	}
	
	
	private void regles() {
		Parser<List<GRegle>> regles = new ListOf<GRegle>(this.<GRegle>ref("regle"));
		register("regles",regles);
	}
	
	
	
	
	
	private void parserDefExprParser() {
		One<String> id = new One<String>()
			.then(literal("@def_expr_parser"))
			.then(character('('))
			.parser(this.<String>ref("ident"))
			.then(character(')'))
			.then(character(';'));
	
		Parser<GExprParser> parser = new XForm<String, GExprParser>(id) {
			@Override
			public GExprParser transform(ParseContext<?> ctx, String content) {
				return new GExprParser(content);
			}
		};
		
		register("def_expr_parser",parser);
	}
	
	private void parserExprOperand() {
		Two<String, GParser> id = new Two<String, GParser>()
			.then(literal("@expr_operand"))
			.then(character('('))
			.first(this.<String>ref("ident"))
			.then(character(','))
			.second(this.<GParser>ref("gparser"))
			.then(character(')'))
			.then(character(';'));
		
		Parser<GExprOperand> parser = new XForm<Pair<String, GParser>, GExprOperand>(id) {
			@Override
			public GExprOperand transform(ParseContext<?> ctx, Pair<String, GParser> content) {
				return new GExprOperand(content.getFirst(),content.getSecond());
			}
		};
		
		register("expr_operand",parser);
	}
	
	private void parserExprBracket() {
		Three<String,String,String> one = new Three<String, String,String>()
				.then(literal("@expr_bracket"))
				.then(character('('))
				.first(this.<String>ref("ident"))
				.then(character(','))
				.second(this.<String>ref("string"))
				.then(character(','))
				.third(this.<String>ref("string"))
				.then(character(')'))
				.then(character(';'));
		
		Parser<GExprBracket> parser = new XForm<Triple<String,String,String>, GExprBracket>(one) {
			@Override
			public GExprBracket transform(ParseContext<?> ctx,
					Triple<String, String,String> content) {
				return new GExprBracket(content);
			}
		};
		register("expr_bracket", parser);
	}
	
	
	private void parserExprOperator() {
		Five<String, String, String, String, GParser> five = new Five<String, String, String, String, GParser>()
			.then(literal("@expr_operator"))
			.then(character('('))
			.first(this.<String>ref("ident"))
			.then(character(','))
			.second(new RegexpString("prefix|infix|postfix"))
			.then(character(','))
			.third(new RegexpString("none|left|right"))
			.then(character(','))
			.fourth(new RegexpString("[0-9]+"))
			.then(character(','))
			.fifth(this.<GParser>ref("gparser"))
			.then(character(')'))
			.then(character(';'))
			;
		
		Parser<GExprOperator> parser = new XForm<Quintuple<String, String, String, String, GParser>, GExprOperator>(five) {
			@Override
			public GExprOperator transform(ParseContext<?> ctx, Quintuple<String, String, String, String, GParser> content) {
				return new GExprOperator(content);
			}
		};
		
		register("expr_operator",parser);
	}
	
	
	private void parserGrammar() {
		// listof orElse 
		
		Choice<GrammarTree> choice = new Choice<GrammarTree>();
		choice.either(this.<GrammarTree>ref("keywords"));
		choice.orElse(this.<GrammarTree>ref("regle"));
		choice.orElse(this.<GrammarTree>ref("def_expr_parser"));
		choice.orElse(this.<GrammarTree>ref("expr_bracket"));
		choice.orElse(this.<GrammarTree>ref("expr_operand"));
		choice.orElse(this.<GrammarTree>ref("expr_operator"));
		choice.orElse(this.<GrammarTree>ref("meta"));
		
		ListOf<GrammarTree> listof = new ListOf<GrammarTree>(choice);
		
		this.setMainParser(new One<List<GrammarTree>>().then(literal("@start")).parser(listof).then(literal("@end")));
		//this.setMainParser(this.<GrammarTree>ref("regle"));
	}
	
	
	
	private void parserMeta() {
		Two<String, String> two = new Two<String, String>()
				.then(literal("@meta"))
				.then(character('('))
				.first(this.<String>ref("ident"))
				.then(character(','))
				.second(this.<String>ref("string"))
				.then(character(')'))
				.then(character(';'));
		Parser<GMeta> parser = new XForm<Pair<String,String>, GMeta>(two) {
			@Override
			public GMeta transform(ParseContext<?> ctx,
					Pair<String, String> content) {
				return new GMeta(content.getFirst(), content.getSecond()); 
			}
		};
		register("meta",parser);
	}
	
	
	
	//
	// =============================================================================================
	//
	private static String streamToString(String string) throws IOException {
		DataInputStream dis =  new DataInputStream(new FileInputStream(string));
		StringBuilder sb = new StringBuilder();
		byte[] b = new byte[2048];
		while((dis.read(b)) >= 0) {
			sb.append(new String(b));
		} ;
		return sb.toString();
	}
	
	
	public static void main(String[] args) {
		try {
			GenericGrammar generic = new GenericGrammar();
			//generic.setMainParser(generic.fetch("keywords"));
			System.out.println(generic.toString());
			StringParseInput input = new StringParseInput(streamToString("testgengram.txt"));
			DefaultParseContext ctx = new DefaultParseContext();
			ParseResult<?> obj = generic.parse(ctx,input);
			if(obj.isError()) {
				System.out.println(ctx.deepest().getDetailedErrorMessage());
				System.out.println(ctx.deepest().getErrorMessage());
			}
			else if(obj.isNull()) {
				System.out.println("C un NULL");
			}
			else {
				System.out.println(obj.getResult());
				GrammarGenerator gen = new GrammarGenerator();
				List<GrammarTree> result = (List<GrammarTree>) obj.getResult();
				gen.init(result);
				System.out.println("Grammar: "+gen);
				input = new StringParseInput(streamToString("testexgram.txt"));
				ParseResult<?> tree = gen.treatment(input);
				if(tree.isError()) {
					System.out.println(tree.toString());
				}
				else if(tree.isNull()){
					System.out.println("NULL");
				}
				else {
					System.out.println("C OK");
					System.out.println(tree.getResult());
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (GrammarVisitorException e) {
			e.printStackTrace();
		}
	}
}
